﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Promote
{
    public enum Direction
    {
        Up,
        Down,
        Left,
        Right
    }

    public enum TileState
    {
        Birth,
        Live,
        Rebirth,
        Dead
    }

    public class BindingData
    {
        public BindingData(int size)
        {
            int n = size * size;
            this.size = size;
            tilesData = new Tile[n];
            for (int i=0; i<n; i++)
            {
                tilesData[i] = new Tile(0, 0, 0);
            }
        }

        public void Update(Grid grid)
        {
            int n = 0;
            Tile tile;
            for (int i = 0; i < size; i++ )
            {
                for (int j = 0; j < size; j++ )
                {
                    tile = grid.Map[i][j];
                    if (tile==null)
                    {
                        tilesData[n].Value = -1;
                    }
                    else
                    {
                        tilesData[n].Value = tile.Value;
                        {
                            System.Windows.Controls.Grid g = ((MainWindow)Application.Current.MainWindow).tileGrid;
                            System.Windows.Controls.Label l = g.Children[n] as System.Windows.Controls.Label;
                            try
                            {

                                if (tile.State == TileState.Rebirth)
                                {

                                    Storyboard sb = (Storyboard)Application.Current.MainWindow.Resources["tileStoryboard_rebirth"];
                                    l.BeginStoryboard(sb);

                                }
                                else if (tile.State == TileState.Birth)
                                {
                                    Storyboard sb = (Storyboard)Application.Current.MainWindow.Resources["tileStoryboard_birth"];
                                    l.BeginStoryboard(sb);
                                }

                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine(ex.Message);
                            }
                        }
                    }

                    n++;
                }
            }
        }

        public int Size { get { return size; } }

        public Tile[] tilesData;
        private int size;
    }

    public class ScoreManager : INotifyPropertyChanged
    {
        public ScoreManager()
        { }
        public uint Score
        {
            get
            {
                return this.score;
            }
            set
            {
                if(this.score != value)
                {
                    score = value;
                    OnPropertyChanged("Score");
                    if(score > bestScore)
                    {
                        bestScore = score;
                        OnPropertyChanged("BestScore");
                    }
                }
            }
        }

        public uint BestScore
        {
            get
            {
                return this.bestScore;
            }
        }

        private uint score;
        private uint bestScore;

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propertyName)
        {
            if(this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class GameManager
    {
        public GameManager(int size)
        {
            this.size = size;
            grid = new Grid(size);
            bindData = new BindingData(size);
            socreManager = new ScoreManager();
            r = new Random();
        }

        public void Start()
        {
            Score.Score = 0;
            isWin = false;
            IsGameOver = false;
            grid.Reset();
            for (int i = 0; i < startTiles; i++)
            {
                AddRandomTile();
            }
            //for(int i=0; i<16;i++)
            //{
            //    Cell cell = grid.RandomAvailableCell();
            //    Tile tile = new Tile(cell, i % 11);
            //    grid.AddTile(tile);
            //}
            //bindData.Update(grid);
        }

        static public string TilesRule(int index)
        {
            if (index<0 || index>tilesRule.Count())
            {
                return "";
            }
            return tilesRule[index];
        }

        public Tile AddRandomTile()
        {
            if (!grid.IsCellAvailable)
            {
                return null;
            }

            int val = r.Next(10) < 9 ? 0 : 1;
            Tile tile = new Tile(grid.RandomAvailableCell(), val);
            grid.AddTile(tile);

            bindData.Update(grid);
            return tile;
        }

        private Cell GetTargetCell(int i, int j, Direction dir)
        {
            int row = 0;
            int col = 0;

            if(i>=size || j>=size)
            {
                return null;
            }

            switch (dir)
            {
                case Direction.Up: col = i; row = j; break;
                case Direction.Down: col = i; row = size - j - 1; break;
                case Direction.Left: row = i; col = j; break;
                case Direction.Right: row = i; col = size - j - 1; break;
            }

            return new Cell(col, row);
        }

        private Cell GetNextCell(Cell cell, Direction dir)
        {
            int dx = 0;
            int dy = 0;
            switch (dir)
            {
                case Direction.Up: dx = 0; dy = 1; break;
                case Direction.Down: dx = 0; dy = -1; break;
                case Direction.Left: dx = 1; dy = 0; break;
                case Direction.Right: dx = -1; dy = 0; break;
            }

            int x = cell.X;
            int y = cell.Y;
            x += dx;
            y += dy;

            if(x>=size || y>=size)
            {
                return null;
            }
            else
            {
                return new Cell(x, y);
            }
        }

        public void Move(Direction dir)
        {
            Tile currentTile;
            Tile TileTo;
            Cell currentCell;
            Cell validCell;
            bool hasChange = false;
            for (int i = 0; i < size; i++ )
            {
                currentCell = validCell = null;
                currentTile = TileTo = null;
                for (int j = 0; j < size; j++ )
                {
                    currentCell = GetTargetCell(i, j, dir);
                    currentTile = grid.Map[currentCell.Y][currentCell.X];

                    if(currentTile == null)
                    {
                        if (validCell == null)
                        {
                            validCell = currentCell;
                        }
                    }
                    else
                    {
                        currentTile.State = TileState.Live;
                        if(TileTo == null)
                        {
                            if(validCell != null)
                            {
                                hasChange = true;
                                grid.MoveTile(currentTile, validCell);
                                validCell = GetNextCell(validCell, dir);
                            }
                            TileTo = currentTile;
                        }
                        else
                        {
                            if(currentTile.Value == TileTo.Value)
                            {
                                hasChange = true;
                                grid.MergerTile(currentTile, TileTo);

                                UpdateScore(TileTo.Value);

                                TileTo = null;
                                if(validCell == null)
                                {
                                    validCell = currentCell;
                                }
                            }
                            else
                            {
                                if(validCell!=null)
                                {
                                    hasChange = true;
                                    grid.MoveTile(currentTile, validCell);
                                    validCell = GetNextCell(validCell, dir);    
                                }

                                TileTo = currentTile;
                            }
                        }
                    }
                }
            }

            if (hasChange)
            {
                AddRandomTile();
                bindData.Update(grid);

                if(!isWin && IsGameWin)
                {
                    if(MessageBox.Show(Application.Current.MainWindow,"游戏结束，成功晋级！\n\n继续吗？","Win",MessageBoxButton.YesNo)
                        == MessageBoxResult.No)
                    {
                        Start();
                    }
                    else
                    {
                        isWin = true;
                    }
                }
            }

            MonitorWork();
            if(IsGameOver)
            {
                MessageBox.Show("游戏失败！");
                Start();
            }
        }

        private void UpdateScore(int value)
        {
            Score.Score += (uint)(value*value);
        }

        public bool IsGameWin
        {
            get
            {
                return grid.GetMaxTileValue() >= 10;
            }
        }

        private void MonitorWork()
        {
            if(grid.IsCellAvailable)
            {
                IsGameOver = false;
                return;
            }

            for(int i=0;i<size;i++)
            {
                for(int j=0; j<size-1; j++)
                {
                    if((grid.Map[i][j].Value == grid.Map[i][j+1].Value) &&
                        (grid.Map[i][j].Value!=-1))
                    {
                        IsGameOver = false;
                        return;
                    }
                }
            }

            for (int j = 0; j < size; j++ )
            {
                for(int i=0; i<size-1; i++)
                {
                    if ((grid.Map[i][j].Value == grid.Map[i+1][j].Value) &&
                        (grid.Map[i][j].Value != -1))
                    {
                        IsGameOver = false;
                        return;
                    }
                }
            }

            IsGameOver = true;
        }

        public Grid grid { get; set; }
        public BindingData BindData { get { return bindData; } }
        public ScoreManager Score { get { return socreManager; } }
        public Boolean IsGameOver { get; set; }

        private BindingData bindData;
        private ScoreManager socreManager;
        private int size;
        private int startTiles = 2;
        private Boolean isWin = false;
        private Random r;

        //"0.新兵","1.少尉","2.中尉","3.上尉","4.少校","5.中校","6.上校","7.大校","8.少将","9.中将","10.上将","11.总司令"
        static private string[] tilesRule = {"0","1","2","3","4","5","6","7","8","9","10","11"};
    }

    public class Grid
    {
        public Grid(int size)
        {
            this.size = size;
            tiles = new List<Tile>();
            r = new Random();

            map = new Tile[size][];
            for (int i = 0; i < size; i++)
            {
                map[i] = new Tile[size];
            }
        }

        public bool AddTile(Tile tile)
        {
            if (IsCellAvailable)
            {
                foreach(Tile t in tiles)
                {
                    if(t.State == TileState.Birth)
                    {
                        t.State = TileState.Live;
                        break;
                    }
                }

                tile.State = TileState.Birth;
                tiles.Add(tile);
                if (map[tile.Y][tile.X] == null)
                {
                    map[tile.Y][tile.X] = tile;
                }
                else
                {
                    throw new PromoteException("添加错误的瓷片");
                }
                return true;
            }
            return false;
        }

        public void MoveTile(Tile tile, Cell cell)
        {
            map[tile.Y][tile.X] = null;
            map[cell.Y][cell.X] = tile;
            tile.X = cell.X;
            tile.Y = cell.Y;
        }

        public void MergerTile(Tile tileFrom, Tile tileTo)
        {
            if((tileFrom==null) ||
                (tileTo==null) ||
                (ReferenceEquals(tileFrom, tileTo)) ||
                (tileFrom==tileTo) ||
                (tileFrom.Value!=tileTo.Value) ||
                ((tileFrom.X!=tileTo.X) && (tileFrom.Y!=tileTo.Y)))
            {
                throw new PromoteException("不能合并指定的两个瓷片");
            }

            map[tileFrom.Y][tileFrom.X] = null;
            tileTo.Value++;
            tileTo.State = TileState.Rebirth;

            tileFrom.State = TileState.Dead;
            tiles.Remove(tileFrom);
        }

        public Cell RandomAvailableCell()
        {
            if (AvailableCellsCount<=0)
            {
                return null;
            }

            int n = r.Next(AvailableCellsCount);

            int idx = 0;
            for (int i = 0; i < size; i++ )
            {
                for (int j = 0; j < size; j++ )
                {
                    if (map[i][j] == null)
                    {
                        if (idx++ == n)
                        {
                            return new Cell(j, i);
                        }
                    }
                }
            }

            throw new PromoteException("随机CELL分配存在问题！");
        }

        public void Reset()
        {
            for (int i = 0; i < size; i++ )
            {
                for (int j = 0; j < size; j++ )
                {
                    map[i][j] = null;
                }
            }

            tiles.Clear();
        }

        public bool IsCellAvailable
        {
            get
            {
                return tiles.Count < Size * Size;
            }
        }

        public int AvailableCellsCount
        {
            get
            {
                return (Size * Size - tiles.Count);
            }
        }

        public int GetMaxTileValue()
        {
            return tiles.Max(tile => tile.Value);
        }

        public int Size { get { return size; } }
        public Tile[][] Map { get { return map; } }

        private List<Tile> tiles;
        private Tile[][] map;
        private int size;
        private Random r;
    }

    public class Tile : DependencyObject
    {
        public static readonly DependencyProperty XProperty ;
        public static readonly DependencyProperty YProperty;
        public static readonly DependencyProperty ValueProperty;
        public static readonly DependencyProperty TitleProperty;
        public static readonly DependencyProperty StateProperty;

        static Tile()
        {
            Tile.XProperty = DependencyProperty.Register("X",
                typeof(int),
                typeof(Tile));

            Tile.YProperty = DependencyProperty.Register("Y",
                typeof(int),
                typeof(Tile));

            Tile.ValueProperty = DependencyProperty.Register("Value",
                typeof(int),
                typeof(Tile));

            Tile.TitleProperty = DependencyProperty.Register("Title",
                typeof(string),
                typeof(Tile));

            Tile.StateProperty = DependencyProperty.Register("State",
                typeof(TileState),
                typeof(Tile));
        }

        public Tile(int x, int y, int val)
        {
            X = x;
            Y = y;
            Value = val;
            State = TileState.Dead;
        }

        public Tile(Cell cell, int val)
        {
            X = cell.X;
            Y = cell.Y;
            Value = val;
            State = TileState.Dead;
        }
        public int Value
        {
            get
            { 
                return (int)GetValue(Tile.ValueProperty);
            }
            set
            {
                SetValue(Tile.ValueProperty, value);
                SetValue(Tile.TitleProperty, GameManager.TilesRule(value));
            }
        }
        public string Title { 
            get
            { 
                return (string)GetValue(Tile.TitleProperty);
            }
        }
        public int X 
        { 
            get { return (int)GetValue(Tile.XProperty); }
            set { SetValue(Tile.XProperty, value); } 
        }
        public int Y 
        {
            get { return (int)GetValue(Tile.YProperty); }
            set { SetValue(Tile.YProperty, value); } 
        }

        public TileState State
        {
            get { return (TileState)GetValue(Tile.StateProperty); }
            set { SetValue(Tile.StateProperty, value); }
        }
    }

    public class Cell
    {
        public Cell(int x, int y)
        {
            X = x;
            Y = y;
        }
        public int X { get; set; }
        public int Y { get; set; }
    }

    public class PromoteException : Exception
    {
        public PromoteException(string message)
        {
            this.message = message;
        }

        public override string Message
        {
            get
            {
                return message;
            }
        }

        private string message;
    }
}
